#include <everest/dbc/odbc/odbc_common.h>
#include <vector>
#include <string.h>
#include <stdio.h>

using namespace NS_EVEREST::dbc::odbc;
using namespace NS_EVEREST::dbc;
using namespace NS_EVEREST;
using namespace std;

OdbcParameter::OdbcParameter(SQLHANDLE hStm,
							 ValueType   vt,
							 SQLSMALLINT inout,
							 SQLULEN colsize,
							 SQLSMALLINT decimalDigits)
	: m_StmHandle(hStm)
	, m_Index(0)
	, m_InOut(inout)
	, m_DecDigits(decimalDigits)
	, m_ColSize(colsize)
	, m_LenOrInd(0)
	, m_buflen(0)
	, m_pBuffer(NULL)
	, m_bBufferOwner(true)
{
	switch ( vt ) {
	case VT_INT: {
		this->m_pBuffer = new char[sizeof(int)];
		this->m_buflen  = sizeof(int);
		*((int*)this->m_pBuffer) = 0;
		this->m_ParamType = SQL_INTEGER;
		this->m_DataType  = SQL_C_SLONG;
		break;
	}
	case VT_BIGINT:{
		this->m_pBuffer = new char[sizeof(double)];
		this->m_buflen = sizeof(int64_t);
		*(double *)this->m_pBuffer = 0;
		this->m_ParamType = SQL_BIGINT;
		this->m_DataType  = SQL_C_SBIGINT;
		break;
	}
	case VT_FLOAT:{
		this->m_pBuffer = new char[sizeof(float)];
		this->m_buflen  = sizeof(float);
		*((float*)this->m_pBuffer) = 0;
		this->m_ParamType = SQL_FLOAT;
		this->m_DataType  = SQL_C_FLOAT;
		break;
	}
	case VT_DOUBLE: {
		this->m_pBuffer = new char[sizeof(double)];
		this->m_buflen  = sizeof(double);
		*((double*)this->m_pBuffer) = 0;
		this->m_ParamType = SQL_DOUBLE;
		this->m_DataType  = SQL_C_DOUBLE;
		break;
	}
	case VT_TIMESTAMP: {
		this->m_pBuffer = new char[sizeof(TIMESTAMP_STRUCT)];
		this->m_buflen  = sizeof(TIMESTAMP_STRUCT);
		memset(this->m_pBuffer, 0, sizeof(TIMESTAMP_STRUCT));
		this->m_ParamType = SQL_TYPE_TIMESTAMP;
		this->m_DataType  = SQL_C_TYPE_TIMESTAMP;
		break;
	}
	case VT_STRING: {
		this->m_pBuffer = NULL;
		this->m_buflen  = 0;
		this->m_DataType = SQL_C_CHAR;
		this->m_ParamType = SQL_VARCHAR;
		break;
	}
	case VT_BINARY: {
		this->m_pBuffer = NULL;
		this->m_buflen = 0;
		this->m_DataType = SQL_C_BINARY;
		this->m_ParamType = SQL_VARBINARY;
		break;
	}
	default:
		this->m_pBuffer = NULL;
		this->m_buflen  = 0;
		this->m_ParamType = SQL_TYPE_NULL;
		this->m_DataType  = SQL_TYPE_NULL;
		break;
	}

	return;
}

OdbcParameter::~OdbcParameter()
{
	if ( this->m_pBuffer && this->m_bBufferOwner) {
		delete[] this->m_pBuffer;
		this->m_pBuffer = NULL;
		this->m_buflen = 0;
	}
	return ;
}

ValueType OdbcParameter::getParamType() const
{
	switch (this->m_DataType)
	{
	case SQL_C_CHAR:   return VT_STRING;
	case SQL_C_SLONG:  return VT_INT;
	case SQL_C_SBIGINT: return VT_BIGINT;
	case SQL_C_FLOAT:  return VT_FLOAT;
	case SQL_C_DOUBLE: return VT_DOUBLE;
	case SQL_C_TYPE_TIMESTAMP: return VT_TIMESTAMP;
	case SQL_C_BINARY: return VT_BINARY;
	default:
		return VT_NULL;
	}
}

int OdbcParameter::getIndex() const
{
	return m_Index;
}

void OdbcParameter::setIndex(int idx)
{
	this->m_Index = idx;
}

ParamInOutType OdbcParameter::getInOutType() const
{
	if ( m_InOut == SQL_PARAM_INPUT)
		return PARAM_IN;
	else if ( m_InOut == SQL_PARAM_OUTPUT)
		return PARAM_OUT;
	else if ( m_InOut == SQL_PARAM_INPUT_OUTPUT)
		return PARAM_IN_OUT;
	else
		return PARAM_INOUT_UNKNOWN;
}

void  OdbcParameter::setInt(int value)
{
	switch ( this->m_DataType ) {
	case SQL_C_SLONG:
		*((int*)m_pBuffer) = value; break;
	case SQL_C_SBIGINT:
		*((int64_t*)m_pBuffer) = value; break;
	case SQL_C_FLOAT:
		*((float*)m_pBuffer) = value; break;
	case SQL_C_DOUBLE:
		*((double*)m_pBuffer) = value; break;
	default: {
			char err[64];
			snprintf(err, 64, "can't set int value to sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::setInt()");
		}
	}
	return ;
}

void  OdbcParameter::setInt64(int64_t value)
{
	switch ( this->m_DataType ) {
	case SQL_C_SLONG:
		*((int*)m_pBuffer) = (int)value; break;
	case SQL_C_SBIGINT:
		*((int64_t*)m_pBuffer) = value; break;
	case SQL_C_FLOAT:
		*((float*)m_pBuffer) = value; break;
	case SQL_C_DOUBLE:
		*((double*)m_pBuffer) = value; break;
	default: {
			char err[64];
			snprintf(err, 64, "can't set int64 value to sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::setInt64()");
		}
	}
	return ;
}

void  OdbcParameter::setFloat(float value)
{
	switch ( this->m_DataType ) {
	case SQL_C_SLONG:
		*((int*)m_pBuffer) = (int)value; break;
	case SQL_C_SBIGINT:
		*((int64_t*)m_pBuffer) = (int64_t)value; break;
	case SQL_C_FLOAT:
		*((float*)m_pBuffer) = value; break;
	case SQL_C_DOUBLE:
		*((double*)m_pBuffer) = value; break;
	default: {
			char err[64];
			snprintf(err, 64, "can't set float value to sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::setFloat()");
		}
	}
	return ;
}

void  OdbcParameter::setDouble(double value)
{
	switch ( this->m_DataType ) {
	case SQL_C_SLONG:
		*((int*)m_pBuffer) = (int)value; break;
	case SQL_C_SBIGINT:
		*((int64_t*)m_pBuffer) = (int64_t)value; break;
	case SQL_C_FLOAT:
		*((float*)m_pBuffer) = (float)value; break;
	case SQL_C_DOUBLE:
		*((double*)m_pBuffer) = value; break;
	default: {
			char err[64];
			snprintf(err, 64, "can't set double value to sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::setDouble()");
		}
	}
	return ;
}

void  OdbcParameter::setString(const String& value)
{
	switch ( this->m_DataType ) {
	case SQL_C_CHAR:
	case SQL_C_BINARY: {
		size_t len = value.length() + 1;
		if ( len > (size_t)this->m_buflen) {
			if(this->m_pBuffer) delete[] this->m_pBuffer;
			this->m_pBuffer = new char[len];
			this->m_buflen = len;
			this->bind();   // rebind because buffer was reallocated
		}
		memcpy(this->m_pBuffer, value.c_str(), this->m_buflen);
		break;
	}
	default: {
			char err[64];
			snprintf(err, 64, "can't set string value to sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::setString(Str)");
		}
	}
	return ;
}
void  OdbcParameter::setString(const char *pValue)
{
	switch ( this->m_DataType ) {
	case SQL_C_CHAR:
	case SQL_C_BINARY: {
		size_t len = pValue?strlen(pValue):0;
		len += 1;
		if ( len > (size_t)this->m_buflen && len > 0) {
			if(this->m_pBuffer) delete[] this->m_pBuffer;
			this->m_pBuffer = new char[len];
			this->m_buflen = len;
			this->bind();   // rebind because buffer was reallocated
		}
		memcpy(this->m_pBuffer, pValue, this->m_buflen);
		break;
	}
	default: {
			char err[64];
			snprintf(err, 64, "can't set cstring value to sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::setString(CStr)");
		}
	}
	return;
}

void  OdbcParameter::setBinary(const void *pValue, size_t len)
{
	switch ( this->m_DataType ) {
	case SQL_C_CHAR:
	case SQL_C_BINARY: {
		if ( len > (size_t)this->m_buflen && len > 0) {
			if(this->m_pBuffer) delete[] this->m_pBuffer;
			this->m_pBuffer = new char[len];
			this->m_buflen = len;
			this->bind();   // rebind because buffer was reallocated
		}
		memcpy(this->m_pBuffer, pValue, this->m_buflen);
		break;
	}
	default: {
			char err[64];
			snprintf(err, 64, "can't set binary(raw) value to sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::setBinary(const void*)");
		}
	}
	return;
}
void  OdbcParameter::setBinary(const ByteVector& bytes)
{
	switch ( this->m_DataType ) {
	case SQL_C_CHAR:
	case SQL_C_BINARY: {
		size_t len = bytes.size();
		if ( len > (size_t)this->m_buflen && len > 0) {
			if(this->m_pBuffer) delete[] this->m_pBuffer;
			this->m_pBuffer = new char[len];
			this->m_buflen = len;
			this->bind();   // rebind because buffer was reallocated
		}
		memcpy(this->m_pBuffer, &bytes[0], this->m_buflen);
		break;
	}
	default: {
			char err[64];
			snprintf(err, 64, "can't set binary(vec) value to sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::setBinary(vec)");
		}
	}
	return;
}

void  OdbcParameter::setDateTime(const DateTime& dt)
{
	switch ( this->m_DataType ) {
	case SQL_C_TYPE_TIMESTAMP: {
		TIMESTAMP_STRUCT *pt = (TIMESTAMP_STRUCT *)this->m_pBuffer;
		pt->year = dt.tm_year + 1900;
		pt->month = dt.tm_mon + 1;
		pt->day  = dt.tm_mday;
		pt->hour = dt.tm_hour;
		pt->minute = dt.tm_min;
		pt->second = dt.tm_sec;
		pt->fraction = 0;
		break;
	}
	default: {
		char err[64];
		snprintf(err, 64, "can't set datetime value to sqltype(%d)", this->m_DataType);
		throw OdbcException(-1, err, "OdbcParameter::setDateTime()");
	}
	} // end switch
	return;
}

void  OdbcParameter::setTimestamp(const Timestamp& ts)
{
	switch ( this->m_DataType ) {
	case SQL_C_TYPE_TIMESTAMP: {
		TIMESTAMP_STRUCT *pt = (TIMESTAMP_STRUCT *)this->m_pBuffer;
		struct tm dt;
		localtime_r((time_t*)&ts.tv_sec, &dt);
		pt->year  = dt.tm_year + 1900;
		pt->month = dt.tm_mon + 1;
		pt->day   = dt.tm_mday;
		pt->hour  = dt.tm_hour;
		pt->minute = dt.tm_min;
		pt->second = dt.tm_sec;
		pt->fraction = ts.tv_usec * 1000;
		break;
	}
	default: {
		char err[64];
		snprintf(err, 64, "can't set timestamp value to sqltype(%d)", this->m_DataType);
		throw OdbcException(-1, err, "OdbcParameter::setTimestamp()");
	}
	} // end switch
	return;
}

bool   OdbcParameter::getInt(int& value)
{
	switch ( this->m_DataType ) {
	case SQL_C_SLONG: {
		value = *((int *)m_pBuffer); break;
	}
	case SQL_C_SBIGINT:{
		value = (int)(*((int64_t *)m_pBuffer)); break;
	}
	case SQL_C_FLOAT:{
		value = (int)(*((float *)m_pBuffer)); break;
	}
	case SQL_C_DOUBLE:{
		value = (int)(*((double *)m_pBuffer)); break;
	}
	default: {
		char err[64];
		snprintf(err, 64, "can't get int value from sqltype(%d)", this->m_DataType);
		throw OdbcException(-1, err, "OdbcParameter::getInt()");
	}
	} // end switch
	return true;
}

bool OdbcParameter::getInt64(int64_t& value)
{
	switch ( this->m_DataType ) {
	case SQL_C_SLONG:
		value = *((int*)m_pBuffer); break;
	case SQL_C_SBIGINT:
		value = *((int64_t*)m_pBuffer); break;
	case SQL_C_FLOAT:
		value = (int64_t)*((float*)m_pBuffer); break;
	case SQL_C_DOUBLE:
		value = (int64_t)*((double*)m_pBuffer); break;
	default: {
			char err[64];
			snprintf(err, 64, "can't get int64 value from sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::getInt64()");
		}
	}
	return true;
}

bool OdbcParameter::getFloat(float& value)
{
	switch ( this->m_DataType ) {
	case SQL_C_SLONG:
		value = (float)*((int*)m_pBuffer); break;
	case SQL_C_SBIGINT:
		value = (float)*((int64_t*)m_pBuffer); break;
	case SQL_C_FLOAT:
		value = (float)*((float*)m_pBuffer) ; break;
	case SQL_C_DOUBLE:
		value = (float)*((double*)m_pBuffer); break;
	default: {
			char err[64];
			snprintf(err, 64, "can't get float value from sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::getFloat()");
		}
	}
	return true;
}

bool   OdbcParameter::getDouble(double& value)
{
	switch ( this->m_DataType ) {
	case SQL_C_SLONG:
		value = (double)*((int*)m_pBuffer); break;
	case SQL_C_SBIGINT:
		value = (double)*((int64_t*)m_pBuffer); break;
	case SQL_C_FLOAT:
		value = (double)*((float*)m_pBuffer) ; break;
	case SQL_C_DOUBLE:
		value = (double)*((double*)m_pBuffer); break;
	default: {
			char err[64];
			snprintf(err, 64, "can't get double value from sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::getDouble()");
		}
	}
	return true;
}

bool   OdbcParameter::getString(String& value)
{
	bool result = true;
	switch ( this->m_DataType ) {
	case SQL_C_CHAR: {
		char buf[256];
		bool strInited = false;
		SQLLEN index = 0;

		while (true) {
			SQLRETURN ret = SQLGetData(m_StmHandle, m_Index, this->m_DataType, buf, 256, &m_LenOrInd);
			if ( ret == SQL_INVALID_HANDLE) {
				throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcParameter::getString()");
			} else if ( ret == SQL_ERROR ) {
				throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcParameter::getString()");
			} else if ( ret == SQL_NO_DATA) {
				break; // all data has returned
			} else if (EVEREST_ODBC_SUCCESS(ret)) { // success or success_with_info
				if ( m_LenOrInd == SQL_NO_TOTAL ) {
					break;  // all data has returned
				} else if ( m_LenOrInd == SQL_NULL_DATA) {
					result = false; // param value is null
					break;
				} else {
					if ( !strInited ) {
						value.reserve(m_LenOrInd + 1);
						strInited = true;
					}
					m_LenOrInd = ( m_LenOrInd > 256 )?256:m_LenOrInd;
					value.append(buf, m_LenOrInd);
					index += m_LenOrInd;
				}
			} // end if
		} // end while
		break;
	}
	default: {
			char err[64];
			snprintf(err, 64, "can't get string value from sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::getString(Str)");
		}
	}
	return result;
}

bool   OdbcParameter::getBinary(ByteVector& bytes)
{
	bool result = true;
	switch ( this->m_DataType ) {
	case SQL_C_BINARY:
	case SQL_C_CHAR: {
		char buf[256];
		bool vecInited = false;
		SQLLEN index = 0;

		while (true) {
			SQLRETURN ret = SQLGetData(m_StmHandle, m_Index, this->m_DataType, buf, 256, &m_LenOrInd);
			if ( ret == SQL_INVALID_HANDLE) {
				throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcParameter::getBinary()");
			} else if ( ret == SQL_ERROR ) {
				throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcParameter::getBinary()");
			} else if ( ret == SQL_NO_DATA) {
				break; // all data has returned
			} else if (EVEREST_ODBC_SUCCESS(ret)) { // success or success_with_info
				if ( m_LenOrInd == SQL_NO_TOTAL ) {
					break;  // all data has returned
				} else if ( m_LenOrInd == SQL_NULL_DATA) {
					result = false; // param value is null
					break;
				} else {
					if ( !vecInited ) {
						bytes.resize(m_LenOrInd);
						vecInited = true;
					}
					m_LenOrInd = ( m_LenOrInd > 256 )?256:m_LenOrInd;
					memcpy(&bytes[index], buf, m_LenOrInd);
					index += m_LenOrInd;
				}
			} // end if
		} // end while
		break;
	}
	default: {
			char err[128];
			snprintf(err, 128, "can't get binary from sqltype(%d)", this->m_DataType);
			throw OdbcException(-1, err, "OdbcParameter::getBinary(vec)");
		}
	}
	return result;
}
bool   OdbcParameter::getDateTime(DateTime& dt)
{
	bool result = false;
	switch ( this->m_DataType ) {
	case SQL_C_TYPE_TIMESTAMP:
		if ( this->m_LenOrInd == SQL_NULL_DATA) {
			result = false;
		} else {
			TIMESTAMP_STRUCT *pt = (TIMESTAMP_STRUCT *)this->m_pBuffer;
			dt.tm_year = pt->year - 1900;
			dt.tm_mon = pt->month - 1;
			dt.tm_mday = pt->day;
			dt.tm_hour = pt->hour;
			dt.tm_min  = pt->minute;
			dt.tm_sec  = pt->second;
			result = true;
		}
		break;
	default: {
		char err[64];
		snprintf(err, 64, "can't get datetime value from sqltype(%d)", this->m_DataType);
		throw OdbcException(-1, err, "OdbcParameter::getDateTime()");
	}
	} // end switch
	return result;
}

bool   OdbcParameter::getTimestamp(Timestamp& ts)
{
	bool result = true;
	switch ( this->m_DataType ) {
	case SQL_C_TYPE_TIMESTAMP:
		if ( this->m_LenOrInd == SQL_NULL_DATA) {
			result = false;
		} else  {
			TIMESTAMP_STRUCT *pt = (TIMESTAMP_STRUCT *)this->m_pBuffer;
			time_t t;
			struct tm dt;
			dt.tm_year = pt->year - 1900;
			dt.tm_mon = pt->month - 1;
			dt.tm_mday = pt->day;
			dt.tm_hour = pt->hour;
			dt.tm_min  = pt->minute;
			dt.tm_sec  = pt->second;
			dt.tm_isdst = -1;   // determine by mktime
			t = mktime(&dt);
			ts.tv_sec = t;
			ts.tv_usec = pt->fraction / 1000;
			result = true;
		}
		break;
	default: {
		char err[64];
		snprintf(err, 64, "can't get datetime value from sqltype(%d)", this->m_DataType);
		throw OdbcException(-1, err, "OdbcParameter::getDateTime()");
	}
	} // end switch
	return result;
}

bool OdbcParameter::bind()
{
	SQLRETURN ret = SQL_SUCCESS;
	if ( this->m_pBuffer == NULL ) return true;
	ret = SQLBindParameter(
				this->m_StmHandle,
				this->m_Index,
				this->m_InOut,
				this->m_DataType,
				this->m_ParamType,
				this->m_ColSize,
				this->m_DecDigits,
				(SQLPOINTER)this->m_pBuffer,
				this->m_buflen,
				&this->m_LenOrInd);

	if ( ret == SQL_INVALID_HANDLE)
		throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcParameter::bind()");
	else if ( ret == SQL_ERROR )
		throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcParameter::bind");

	return true;
}
